<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file contains the pretty_xml() helper function for XML strings.
 *
 * PHP version 5
 *
 * This file is part of Majic - A rapid-development web application framework
 * for PHP 5.
 *
 * Majic is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * Majic is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Majic. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package     Majic
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright   2007-2008 Michael J. I. Jackson
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
 * @version     SVN: $Id: xml.php 43 2008-10-27 03:39:14Z mjijackson $
 */

/**
 * Outputs this element as pretty XML to increase readability.
 *
 * @param   string  $xml        The XML string
 * @param   string  $dent       (optional) The character(s) to use for
 *                              indentation of nested elements, defaults to
 *                              "    " (four spaces)
 * @param   string  $delim      (optional) A unique delimiter to use when
 *                              splitting the XML string, defaults to
 *                              "___PRETTY_XML___"
 * @return  string              The "pretty" XML
 */
function pretty_xml($xml, $dent = '    ', $delim = '___PRETTY_XML___')
{
    $indent = 0;
    $pretty = array();
    $xml = trim($xml);

    // get an array containing each XML tag
    $tags = explode($delim, preg_replace('/>\s*</', ">$delim<", $xml));

    // shift off opening XML tag if present
    if (count($tags) && preg_match('/^<\?\s*xml/', $tags[0])) {
        $pretty[] = array_shift($tags);
    }

    foreach ($tags as $tag) {
        if (preg_match('/^<\w+[^>\/]*?>$/', $tag)) {
            // opening tag, print and increase indent
            $pretty[] = str_repeat($dent, $indent) . $tag;
            $indent++;
        } else {
            if (preg_match('/^<\/\w+[^>\/]*?>$/', $tag)) {
                // closing tag, decrease indent and print
                $indent--;
            }
            $pretty[] = str_repeat($dent, $indent) . $tag;
        }
    }

    return implode("\n", $pretty);
}

/**
 * Builds an XML string from an associative array. The array may be nested
 * to represent child elements. The following parameters may be used as keys
 * at each level of the array:
 *
 * ns       (string) The namespace to use for the node
 * tag      (string) The node tag name
 * attr     (array) An associative array of key => value attribute pairs
 *          for the node
 * val      (string) The value of this node
 * cn       (array) An array of child nodes
 *
 * For example, the following definition:
 *
 * array(
 *     'ns'    => 'www',
 *     'tag'   => 'employees',
 *     'cn'    => array(
 *         array(
 *             'tag'   => 'employee',
 *             'cn'    => array(
 *                 array(
 *                     'tag'   => 'param',
 *                     'attr'  => array(
 *                         'name'  => 'name',
 *                         'type'  => 'string'
 *                     ),
 *                     'val'   => 'Michael Jackson'
 *                 ),
 *                 array(
 *                     'tag'   => 'param',
 *                     'attr'  => array(
 *                         'name'  => 'email',
 *                         'type'  => 'string'
 *                     ),
 *                     'val'   => 'mjackson@example.com'
 *                 )
 *             )
 *         ),
 *         array(
 *             'tag'   => 'employee',
 *             'cn'    => array(
 *                 array(
 *                     'tag'   => 'param',
 *                     'attr'  => array(
 *                         'name'  => 'name',
 *                         'type'  => 'string'
 *                     ),
 *                     'val'   => 'Dan Davis'
 *                 ),
 *                 array(
 *                     'tag'   => 'param',
 *                     'attr'  => array(
 *                         'name'  => 'email',
 *                         'type'  => 'string'
 *                     ),
 *                     'val'   => 'ddavis@example.com'
 *                 )
 *             )
 *         )
 *     )
 * );
 *
 * will produce the following XML:
 *
 * <www:employees>
 *     <employee>
 *         <param name="name" type="string">Michael Jackson</param>
 *         <param name="email" type="string">mjackson@example.com</param>
 *     </employee>
 *     <employee>
 *         <param name="name" type="string">Dan Davis</param>
 *         <param name="email" type="string">ddavis@example.com</param>
 *     </employee>
 * </www:employees>
 *
 * As you can see, this function is rather tedious and should probably only be
 * used as the underlying engine for a higher level method.
 *
 * @param   array   $def            The XML definition (see above)
 * @return  string                  The XML
 */
function build_xml($def = array())
{
    if (empty($def)) {
        return '';
    }
    if (is_string($def)) {
        return $def;
    }

    // add namespace and tag
    $nstag = (isset($def['ns']) ? $def['ns'] . ':' : '') . $def['tag'];
    $xml = '<' . $nstag;

    // add attributes
    if (isset($def['attr']) && is_array($def['attr'])) {
        foreach ($def['attr'] as $key => $value) {
            $xml .= ' ' . $key . '="' . $value . '"';
        }
    }

    $empty = true;

    // add value
    if (!empty($def['val'])) {
        $empty = false;
        $xml .= '>';
        $xml .= $def['val'];
    }

    // add child nodes
    if (!empty($def['cn']) && is_array($def['cn'])) {
        if ($empty) {
            $empty = false;
            $xml .= '>';
        }
        foreach ($def['cn'] as $child) {
            $xml .= build_xml($child);
        }
    }

    if ($empty) {
        return $xml . ' />'; // empty node
    }

    return $xml . '</' . $nstag . '>';
}

?>
